package fun.tan90.device.transfer.plugin.handler;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import fun.tan90.device.event.EventTopic;
import fun.tan90.device.event.model.DataHandleFailure;
import fun.tan90.device.event.model.DataHandleSuccess;
import fun.tan90.device.event.model.DeviceSourceData;
import fun.tan90.device.event.model.DeviceTransferData;
import fun.tan90.device.transfer.plugin.config.BuiltInSys;
import fun.tan90.device.transfer.plugin.model.Filter;
import fun.tan90.device.transfer.plugin.model.Rule;
import fun.tan90.device.transfer.plugin.model.Var;
import lombok.extern.slf4j.Slf4j;
import org.noear.dami.Dami;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.bean.LifecycleBean;
import org.noear.solon.core.util.DateUtil;
import org.noear.solon.expression.snel.SnEL;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import static fun.tan90.device.event.threadpool.ThreadPool.SINK_FIXED_EXECUTOR;

@Slf4j
@Component
public class TransferSubHandler implements LifecycleBean {

    private static final String TRANSFER_RULE_KEY = "{}_{}";

    private static final Map<String, Rule> TRANSFER_RULES = new HashMap<>();

    private static final Map<String, Object> CONTEXT = Map.of("_sys", BuiltInSys.class);

    @Inject("${plugin-name}")
    String pluginName;

    @Override
    public void start() {
        ONode transferRules = Dami.<Void, ONode>bus().sendAndRequest(EventTopic.TRANSFER_RULE_TOPIC, null);
        log.info("TransferRule: {}", transferRules.toJson());
        transferRules.ary().forEach(this::buildRule);
        log.info("transferRules: {}", JSONUtil.toJsonStr(TRANSFER_RULES));
    }

    @Override
    public void postStart() {
        Dami.<DeviceSourceData, Void>bus().listen(EventTopic.DEVICE_SOURCE_DATA_TOPIC, payload -> {
            DeviceSourceData sourceData = payload.getContent();
            String deviceCode = sourceData.getDeviceCode();
            String dataSource = sourceData.getDataSource();
            String deviceType = Dami.<String, String>bus().sendAndRequest(EventTopic.DEVICE_INFO_TOPIC, deviceCode);
            String data = sourceData.getData();
            if (StrUtil.isBlank(deviceType)) {
                log.warn("未知设备类型：deviceCode: {}", deviceCode);
                return;
            }
            sourceData.setDeviceType(deviceType);
            Rule rule = TRANSFER_RULES.get(transferRuleKey(deviceType, dataSource));
            if (Objects.isNull(rule)) {
                log.warn("尚未配置transfer_rule 【deviceType:{}, dataSource:{} deviceType: {}】", deviceType, dataSource, deviceCode);
                return;
            }
            log.info("deviceType: {}, dataSource: {}, deviceCode: {}, data: {}", deviceType, dataSource, deviceCode, data);
            ONode root = JSONUtil.isTypeJSON(data) ? ONode.loadStr(data) : Dami.<DeviceSourceData, ONode>bus().sendAndRequest(EventTopic.PROTOCOL_JA_EVAL_TOPIC, sourceData);
            List<Var> map = rule.getMap();
            Map<String, Object> params = map.stream().collect(Collectors.toMap(Var::getVarName, var -> {
                String varName = var.getVarName();
                String jpath = var.getJpath();
                ONode selected = root.select(jpath);
                Object val = toSpecificTypeVal(var.getType(), selected, var.getDefaultVal());
                String formatExpr = var.getFormatExpr();
                if (StrUtil.isNotBlank(formatExpr)) {
                    Map<String, Object> paramMap = new HashMap<>(CONTEXT);
                    paramMap.put(varName, val);
                    val = SnEL.eval(formatExpr, paramMap);
                }
                return val;
            }));
            log.info("before map: {}, after map: {}", root.toJson(), JSONUtil.toJsonStr(params));
            Filter filter = rule.getFilter();
            if (Objects.nonNull(filter)) {
                String expression = filter.getExpression();
                if (Boolean.FALSE.equals(SnEL.eval(expression, params))) {
                    log.info("过滤条件不满足：deviceCode: {}, expression: {} {}", deviceCode, expression, JSONUtil.toJsonStr(params));
                    return;
                }
            }

            Future<?> future = SINK_FIXED_EXECUTOR.submit(() -> {
                Dami.<DeviceTransferData, Void>bus().send(EventTopic.DEVICE_TRANSFER_DATA_TOPIC, new DeviceTransferData(deviceType, dataSource, deviceCode, params));
            });
            try {
                future.get();
                Dami.<DataHandleSuccess, Void>bus().send(EventTopic.DATA_HANDLE_SUCCESS_TOPIC, DataHandleSuccess.builder()
                        .pluginName(pluginName)
                        .deviceCode(deviceCode)
                        .deviceType(deviceType)
                        .dataSource(dataSource)
                        .build());
            } catch (Exception e) {
                Dami.<DataHandleFailure, Void>bus().send(EventTopic.DATA_HANDLE_FAILURE_TOPIC, DataHandleFailure.builder()
                        .pluginName(pluginName)
                        .deviceCode(deviceCode)
                        .deviceType(deviceType)
                        .dataSource(dataSource)
                        .throwable(e)
                        .build());
            }
        });
    }

    @Override
    public void preStop() throws Throwable {
        TRANSFER_RULES.clear();
    }

    private Object toSpecificTypeVal(String type, ONode selected, String defaultVal) {
        boolean isExpr = defaultVal.startsWith("expr:");
        if (isExpr) {
            defaultVal = defaultVal.split(":", 2)[1];
        }
        if ("string".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : defaultVal : selected.getString();
        }
        if ("date".equals(type)) {
            return selected.isNull() ? (isExpr ? SnEL.eval(defaultVal, CONTEXT) : DateUtil.parseTry(defaultVal)) : selected.getDate();
        }
        if ("int".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Integer.parseInt(defaultVal) : selected.getInt();
        }
        if ("long".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Long.parseLong(defaultVal) : selected.getLong();
        }
        if ("float".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Float.parseFloat(defaultVal) : selected.getFloat();
        }
        if ("double".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Double.parseDouble(defaultVal) : selected.getDouble();
        }
        if ("bool".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Boolean.parseBoolean(defaultVal) : selected.getBoolean();
        }
        return isExpr ? SnEL.eval(defaultVal, CONTEXT) : selected.getString();
    }

    private String transferRuleKey(String deviceType, String dataSource) {
        return StrUtil.format(TRANSFER_RULE_KEY, deviceType, dataSource);
    }

    private void buildRule(ONode transferRule) {
        String deviceType = transferRule.get("deviceType").getString();
        String dataSource = transferRule.get("dataSource").getString();
        String map = transferRule.get("map").getString();
        String filter = transferRule.get("filter").getString();
        TRANSFER_RULES.put(transferRuleKey(deviceType, dataSource), Rule.builder()
                .map(JSONUtil.toList(map, Var.class))
                .filter(JSONUtil.toBean(filter, Filter.class))
                .build());
    }

    public static void main(String[] args) {
        Map<String, Object> params = new HashMap<>();
        System.out.println(SnEL.eval("age == null || age > 20", params));
    }
}
